﻿using System.Data;
using System.Reflection;
using NHibernate;
using NHibernate.Context;
using NHibernate.Engine;
using NHibernate.Linq;
using NLite.Reflection;
using NLite.Log;
using System;
using NLite.Data;
using System.Linq;


namespace NLite.NHPersistence
{
    public interface INhibernateSessionFactory : NLite.Persistence.IRepositorySessionFactory
    {
        NHibernate.Cfg.Configuration Cfg { get; }
        ISessionFactory SessionFactory { get; }
       
    }

    public interface INhibernateSession : NLite.Persistence.IRepositorySession
    {
        ISession CurrentSession { get; }
    }

    public class NhibernateSessionFactory : BooleanDisposable, INhibernateSessionFactory
    {
        class NhibernateSession : BooleanDisposable, INhibernateSession
        {
            private ISession currentSession;
            public ISession CurrentSession { get { return currentSession; } }

            public string DbKey { get; set; }

            public NhibernateSession(ISession session)
            {
                currentSession = session;
            }

            public NLite.Persistence.ITransaction Transaction { get; internal set; }

            public NLite.Persistence.ITransaction Begin(IsolationLevel isolationLevel)
            {
                if (Transaction != null
                && !Transaction.WasCommitted
                && !Transaction.WasRolledBack)
                    return Transaction;

                var tx = new NhibernateTransaction(this,currentSession.BeginTransaction(isolationLevel));
                
                Transaction = tx;
                return tx;
            }

            public NLite.Persistence.ITransaction BeginTransaction()
            {
                if (Transaction != null
                && !Transaction.WasCommitted
                && !Transaction.WasRolledBack)
                    return Transaction;

                Transaction = new NhibernateTransaction(this, currentSession.BeginTransaction());

                return Transaction;
            }

           
            public void Close()
            {
                try
                {
                    var tx = currentSession.Transaction;
                    if (tx != null)
                    {
                        if (!tx.WasCommitted && !tx.WasRolledBack)
                            tx.Commit();
                        tx.Dispose();
                        tx = null;
                    }
                    else
                        currentSession.Flush();

                    currentSession.Close();
                    currentSession.Dispose();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                }
            }

            protected override void Dispose(bool disposing)
            {
                if (disposing)
                    Close();
            }

            public IDbConnection Connection
            {
                get
                {
                    return currentSession.Connection;
                }
            }
            public virtual System.Linq.IQueryable<TEntity> Query<TEntity>()
            {
                return currentSession.Query<TEntity>();
            }

            public virtual Persistence.IRepository<TEntity, TId> CreateRepository<TEntity, TId>()
            {
                return new NHRepository<TEntity, TId>(this);
            }

            private IDbCommand CreateCommand(CommandType commandType, string commandText, params object[] args)
            {
                var command = currentSession.Connection.CreateCommand(commandText, args);
                command.CommandType = commandType;

                var tx = Transaction;
              

                if (tx != null && !tx.WasCommitted && !tx.WasRolledBack)
                {
                    try
                    {
                        tx.Enlist(command);
                    }
                    catch (Exception ex)
                    {
                        log.Error(ex.Message, ex);
                    }
                }

                return command;
            }

            public T[] ExecuteList<T>(CommandType commandType, string commandText, params object[] args) where T : new()
            {
                try
                {
                    return CreateCommand(commandType, commandText, args)
                        .ExecuteList<T>()
                        .ToArray();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    return new T[0];
                }
            }

            public int ExecuteNonQuery(CommandType commandType, string commandText, params object[] args)
            {
                try
                {
                    return CreateCommand(commandType, commandText, args)
                        .ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    return -1;
                }
            }

            public IDataReader ExecuteReader(CommandType commandType, string commandText, params object[] args)
            {
                try
                {
                    return CreateCommand(commandType, commandText, args)
                        .ExecuteReader();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    return null;
                }
            }

            public T ExecuteScalar<T>(CommandType commandType, string commandText, params object[] args)
            {
                try
                {
                    return CreateCommand(commandType, commandText, args)
                        .ExecuteScalar<T>();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    return default(T);
                }
            }

            public T[] ExecuteScalarList<T>(CommandType commandType, string commandText, params object[] args)
            {
                try
                {
                    return CreateCommand(commandType, commandText, args)
                        .ExecuteScalarList<T>()
                        .ToArray();
                }
                catch (Exception ex)
                {
                    log.Error(ex.Message, ex);
                    return new T[0];
                }
            }
        }

        class NhibernateTransaction : NLite.Persistence.ITransaction
        {
            private ITransaction Inner;
            private NhibernateSession Session;

            public NhibernateTransaction(NhibernateSession session, ITransaction transaction)
            {
                Inner = transaction;
                Session = session;
            }

            public bool IsActive
            {
                get { return Inner.IsActive; }
            }

            public bool WasCommitted
            {
                get { return Inner.WasCommitted; }
            }

            public bool WasRolledBack
            {
                get { return Inner.WasRolledBack; }
            }

          

            public void Commit()
            {
                Inner.Commit();
                Session.Transaction = null;
            }

            public void Enlist(IDbCommand command)
            {
                Inner.Enlist(command);
            }

            public void Rollback()
            {
                Inner.Rollback();
                Session.Transaction = null;
            }

            public void Dispose()
            {
                Inner.Dispose();
                Session.Transaction = null;
            }
        }

        private static ILog log = LogManager.GetLogger(typeof(NhibernateSessionFactory));

        private NHibernate.ISessionFactory sessionFactory;

        public NhibernateSessionFactory()
        {
            DbKey = "Db";
        }

        public NHibernate.ISessionFactory SessionFactory { get { return sessionFactory; } }
        public NHibernate.Cfg.Configuration Cfg { get; private set; }

     

        public static NhibernateSessionFactory Configure()
        {
            return Configure(null);
        }

        public static NhibernateSessionFactory Configure(string fileName)
        {
            var factory = new NhibernateSessionFactory();

            if(string.IsNullOrEmpty(fileName))
                factory.Cfg = new NHibernate.Cfg.Configuration().Configure();
            else
                factory.Cfg = new NHibernate.Cfg.Configuration().Configure(fileName);
            factory.sessionFactory = factory.Cfg.BuildSessionFactory();

            return factory;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                try
                {
                    sessionFactory.Close();
                    sessionFactory.Dispose();
                    Cfg = null;
                }
                catch { }
            }
        }

        public string DbKey { get; set; }

       

        public Persistence.IRepositorySession OpenSession()
        {
            return new NhibernateSession(sessionFactory.OpenSession()) { DbKey = this.DbKey };
        }


        Persistence.IDbSession Persistence.IDbSessionFactory.OpenSession()
        {
            return OpenSession();
        }
    }
}
